package info.niwota.ziplock;

import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
import java.util.List;
import java.util.regex.Pattern;

import android.content.Context;
import android.util.Log;

/**
 * @author qiangli
 * 
 */
public final class FileUtils {
	private static final File root = SDCard.getRoot();

	public static interface FileVisitor {
		void doAccept(File file);

		void onFile(File file);

		void onFolder(File dir);
	}

	private static final int BUFSIZE = 1024;
	private static final String TAG = "FileUtils";

	public static void copy(File from, File to) throws IOException {
		InputStream in = new FileInputStream(from);
		OutputStream out = new FileOutputStream(to);
		byte[] buf = new byte[BUFSIZE];
		int read = 0;
		try {
			while ((read = in.read(buf)) >= 0) {
				out.write(buf, 0, read);
			}
		} finally {
			closeSilently(out);
			closeSilently(in);
		}
	}

	public static void deleteAll(File top) {
		final FileFilter ff = null;
		final FileVisitor fv = new FileVisitor() {
			public void doAccept(File file) {
			}

			public void onFile(File file) {
				file.delete();
			}

			public void onFolder(File dir) {
				dir.delete();
			}
		};

		walk(top, ff, fv, null);
	}

	private static void find(File dir, Pattern pattern, List<File> out,
			List<File> trail, StatusMonitor status) {
		if (status != null && status.aborted) {
			throw new RuntimeException("Operation aborted.");
		}
		if (dir.isFile()) {
			if (pattern.matcher(dir.getName()).matches()) {
				out.add(dir);
			}
			return;
		}
		if (trail != null) {
			trail.add(dir);
		}
		File[] files = dir.listFiles();
		for (File f : files) {
			find(f, pattern, out, trail, status);
		}
	}

	public static void find(File dir, String pattern, List<File> out,
			List<File> trail, StatusMonitor status) {
		Pattern p = Pattern.compile(pattern);
		find(dir, p, out, trail, status);
	}

	public static void findZip(File top, StatusMonitor status,
			final List<File> found) {
		final FileFilter ff = new FileFilter() {
			public boolean accept(File pathname) {
				String n = pathname.getName();
				return n.endsWith(".zip");
			}
		};
		final FileVisitor fv = new FileVisitor() {
			public void doAccept(File file) {
				found.add(file);
			}

			public void onFile(File file) {
			}

			public void onFolder(File dir) {
			}
		};

		if (Debug.DEBUG) {
			Log.d(TAG, "******** findZip: " + top + " root: " + root);
		}
		status.send(top.getPath());
		//
		walkzip(top, ff, fv, status);
		//
		Collections.sort(found);
	}

	public static void walkzip(File dir, FileFilter filter,
			FileVisitor visitor, StatusMonitor status) {
		if (status != null) {
			if (status.aborted) {
				throw new RuntimeException("Operation aborted.");
			}
			status.send(dir.getName());
		}
		//
		if (dir.isFile()) {
			if (visitor != null) {
				visitor.onFile(dir);
			}
			//
			if (filter != null && visitor != null && filter.accept(dir)) {
				visitor.doAccept(dir);
			}
			return;
		}
		//
		if (Debug.DEBUG) {
			Log.d(TAG, "******** walkzip: " + dir);
		}
		if (dir.equals(root)) {
			return;
		}
		File[] files = dir.listFiles();
		if (files == null) {
			return;
		}
		for (File f : files) {
			walkzip(f, filter, visitor, status);
		}

		// folder
		if (visitor != null) {
			visitor.onFolder(dir);
		}

	}

	public static void walk(File dir, FileFilter filter, FileVisitor visitor,
			StatusMonitor status) {
		if (status != null) {
			if (status.aborted) {
				throw new RuntimeException("Operation aborted.");
			}
			status.send(dir.getName());
		}
		//
		if (dir.isFile()) {
			if (visitor != null) {
				visitor.onFile(dir);
			}
			//
			if (filter != null && visitor != null && filter.accept(dir)) {
				visitor.doAccept(dir);
			}

			return;
		}

		if (Debug.DEBUG) {
			Log.d(TAG, "******** walk: " + dir);
		}

		File[] files = dir.listFiles();
		if (files == null) {
			return;
		}
		for (File f : files) {
			walk(f, filter, visitor, status);
		}

		// folder
		if (visitor != null) {
			visitor.onFolder(dir);
		}

	}
	
	public static void importZip(Context ctx, List<File> zips, boolean delete) {
		final File base = FolderContentProvider.ZIP_BASE;
		if (Debug.DEBUG) {
			Log.d(TAG, "importZip delete: " + delete + " base: " + base
					+ " selected: " + zips);
		}
		if (delete) {
			// rename or copy and delete
			for (File f : zips) {
				try {
					String n = f.getName();
					if (n.endsWith(".zip") == false) {
						continue; //
					}
					File dest = FileUtils.newZipFile(ctx, base, n);
					boolean b = f.renameTo(dest);
					if (!b) {
						if (Debug.DEBUG) {
							Log.d(TAG, "importZip rename failed, copying..." + f);
						}
						FileUtils.copy(f, dest);
						f.delete();
					}
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		} else {
			// copy only
			for (File f : zips) {
				try {
					String n = f.getName();
					if (n.endsWith(".zip") == false) {
						continue; //
					}
					File dest = FileUtils.newZipFile(ctx, base, n);
					FileUtils.copy(f, dest);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	public static File newZipFile(Context ctx, File root, String name) {
		OutputStream os = null;
		try {
			String n = normalize(name);
			File file = new File(root, n);
			file = findUniqueZipFile(file);
			os = new FileOutputStream(file);
			return file;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			closeSilently(os);
		}
		return null;
	}

	/**
	 * 1. replace lf/cr with space 2. trim leading/trailing spaces
	 * 
	 * @param s
	 * @return
	 */
	public static String normalize(String s) {
		if (s == null) {
			return "";
		}
		final String regex = "[\\s]+";
		return s.replaceAll(regex, " ").trim();
	}

	private static File findUniqueZipFile(File file) {
		if (!file.exists() && !new File(file + ".enc").exists()) {
			return file;
		}
		String dir = file.getParent();
		String name = file.getName();
		String ext = "";
		//
		int idx = name.lastIndexOf(".");
		if (idx > 0) {
			ext = name.substring(idx); // including dot
			name = name.substring(0, idx);
		}

		// Try until we find a filename which
		// does not exist yet.
		for (int i = 0; i < Integer.MAX_VALUE; i++) {
			File c1 = new File(dir, name + "-" + i + ext);
			File c2 = new File(dir, name + "-" + i + ext + ".enc");
			boolean exists = (c1.exists() || c2.exists());
			if (!exists) {
				return c1;
			}
		}
		return null;
	}

	public static void closeSilently(Closeable c) {
		if (c == null) {
			return;
		}
		try {
			c.close();
		} catch (Throwable t) {
			// do nothing
		}
	}

	/**
	 * 
	 */
	private FileUtils() {
	}
}
